﻿module FourBallState

open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Input
open Casanova
open Coroutines

[<Measure>]
type m
[<Measure>]
type s
[<Measure>]
type v = m/s
[<Measure>]
type a = v/s

let numCol = 7
let numRow = 5
let dt = 0.05<s>

let totalBall = ref (numCol * numRow)

type Ball =
    {
        Position              : Variable<float<m>>
        Velocity              : Variable<float<v>>
        Acceleration          : Variable<float<a>>
        Time                  : Variable<float<s>>
        Active                : bool ref
        Player                : int
        Col                   : int
        Row                   : int
    }
    member this.EulerSolve() =
        this.Velocity := !this.Velocity + !this.Acceleration * dt
        this.Position := !this.Position + !this.Velocity * dt
        
    member this.RK4Solve( dt : float<s> ) =
        printf "RK4Solve\n"

type GameState = 
    {
        BoardMatrix           : Variable<int[]>
        BallArray             : Variable<Ball[]>
        row                   : Variable<int>
        col                   : Variable<int>
    }

type Player =
    {
        Name                  : Variable<string>
        Score                 : int ref
        Id                    : int
    }

let wHeight = ref 0
let tHeight = ref 0
let quota = ref -1
let victory : int ref = ref 0

let mutable selection : bool = false
let mutable shoot : bool = false
let mutable bounce : bool = false
let checkBall : bool ref = ref true

let game_state = 
    {
        BoardMatrix = Variable(fun () -> Array.zeroCreate (numCol * numRow))
        BallArray =  Variable(fun () -> Array.zeroCreate (numCol * numRow))
        row = Variable(0,0)
        col = Variable(3,3)
    }

let player_one = 
    {
        Name = Variable(fun() -> System.String.Empty)
        Score = ref 0
        Id = 1
    }

let player_two = 
    {
        Name = Variable(fun() -> System.String.Empty)
        Score = ref 0
        Id = -1
    }
    

let a : Variable<KeyboardState> = Variable(fun () ->  Keyboard.GetState())
let b : Variable<KeyboardState> = Variable(fun () ->  Keyboard.GetState())
let turn : Variable<int> = Variable(1,1)

let retriveRefBool(value : bool) : bool ref =
    let outv : bool ref = ref false
    if value = true then
        outv.Value <- true
    outv

let getQuotaByRow(quota : int<1>) : int<m> =
    let retval :  int<m> ref = ref -1<m>
    let b = 4<m>
    let c = 1<m> * quota
    let d = 1<m> * wHeight.Value
    let e = 1<m> * tHeight.Value
    if quota = 4 || quota = 3 || quota = 2 || quota = 1 || quota = 0 then
       retval.Value <- (d - 225<m>) - e - 120<1> * (b - c)
    retval.Value


let update_state() = 
    b := Keyboard.GetState()
    a := b.Value

    if victory.Value = 0 then
        if ((!b).[Keys.Left] = KeyState.Down) && ((!a).[Keys.Left] = KeyState.Up) then
        
            selection <- true

            if(!game_state.col = 0) then
                game_state.col := numCol - 1
            else
                game_state.col := !game_state.col - 1

        else
            game_state.col := !game_state.col        

        if ((!b).[Keys.Right] = KeyState.Down) && ((!a).[Keys.Right] = KeyState.Up) then

            selection <- true

            if(!game_state.col = (numCol - 1)) then
                game_state.col := 0
            else
                game_state.col := !game_state.col + 1

    let rest : bool ref = ref true
    for bbb in !game_state.BallArray do
        if bbb.Active.Value = true then

            let pos : float<m> = float (-getQuotaByRow(4 - quota.Value)) * 1.0<m>

            if (!bbb.Position < (pos + 2.0<m>)) && (!bbb.Position > (pos - 2.0<m>)) && (!bbb.Velocity < 2.0<v>) && (!bbb.Velocity > -2.0<v>) then                
                bbb.Active.Value <- false
                checkBall.Value <- true
                
            if !bbb.Position < pos then
                rest.Value <- false
                bbb.Velocity := !bbb.Velocity * -0.15
                bounce <- true
                bbb.Position := pos
                  
            else
                rest.Value <- true
                

            if rest.Value = true then
                bbb.EulerSolve()

let make_move_callback() =
    if victory.Value = 0 then
        if checkBall.Value = true && (((!b).[Keys.Down] = KeyState.Down) && ((!a).[Keys.Down] = KeyState.Up)) then
            shoot <- true
            let x = ref -1
            for i = (numRow - 1) downto 0 do
                if (!game_state.BoardMatrix).[i * 7 + !game_state.col] = 0 then
                    x.Value <- i


            quota.Value <- x.Value
            checkBall.Value <- false
            totalBall.Value <- totalBall.Value - 1
      
            if quota.Value = -1 then
                checkBall.Value <- true

            if x.Value > -1 then
                if !turn = player_one.Id then
                    let tempArray = !game_state.BoardMatrix
                    tempArray.SetValue(player_one.Id, x.Value * 7 + !game_state.col)
                    game_state.BoardMatrix := tempArray

                    let newball = 
                        {Position = Variable(0.0<m>,0.0<m>); 
                        Velocity = Variable(-100.0<v>, -100.0<v>); 
                        Acceleration = Variable(-20.0<a>,-20.0<a>); 
                        Time = Variable(0.0<s>,0.0<s>);
                        Active = ref true;
                        Player = player_one.Id;
                        Col = !game_state.col;
                        Row = 0}

                    let newballarr = !game_state.BallArray
                    newballarr.SetValue(newball, x.Value * 7 + !game_state.col)
                    game_state.BallArray := newballarr

                    game_state.row := x.Value
                    turn := player_two.Id
                elif !turn = player_two.Id then
                    let tempArray = !game_state.BoardMatrix
                    tempArray.SetValue(player_two.Id, x.Value * 7 + !game_state.col)
                    game_state.BoardMatrix := tempArray

                    let newball = 
                        {Position = Variable(0.0<m>,0.0<m>);
                        Velocity = Variable(-100.0<v>,-100.0<v>); 
                        Acceleration = Variable(-20.0<a>,-20.0<a>); 
                        Time = Variable(0.0<s>,0.0<s>);
                        Active = ref true;
                        Player = player_two.Id;
                        Col = !game_state.col;
                        Row = 0}

                    let newballarr = !game_state.BallArray
                    newballarr.SetValue(newball, x.Value * 7 + !game_state.col)
                    game_state.BallArray := newballarr

                    game_state.row := x.Value
                    turn := player_one.Id
        else
            game_state.BoardMatrix := !game_state.BoardMatrix
            game_state.BallArray := !game_state.BallArray
            game_state.row := !game_state.row
            turn := !turn

let checkResult(value : int) =
    if value = -4 then
        victory.Value <- -1
    if value = 4 then
        victory.Value <- 1

let checkVictory() = 
    let result : int ref = ref 0
    // ROW CHECK
    for r = 0 to 4 do
        result.Value <- (!game_state.BoardMatrix).[r * 7] +  (!game_state.BoardMatrix).[r * 7 + 1] + (!game_state.BoardMatrix).[r * 7 + 2] + (!game_state.BoardMatrix).[r * 7 + 3]
        checkResult(result.Value)
        result.Value <- (!game_state.BoardMatrix).[r * 7 + 1] +  (!game_state.BoardMatrix).[r * 7 + 2] + (!game_state.BoardMatrix).[r * 7 + 3] + (!game_state.BoardMatrix).[r * 7 + 4]
        checkResult(result.Value)
        result.Value <- (!game_state.BoardMatrix).[r * 7 + 2] +  (!game_state.BoardMatrix).[r * 7 + 3] + (!game_state.BoardMatrix).[r * 7 + 4] + (!game_state.BoardMatrix).[r * 7 + 5]
        checkResult(result.Value)
        result.Value <- (!game_state.BoardMatrix).[r * 7 + 3] +  (!game_state.BoardMatrix).[r * 7 + 4] + (!game_state.BoardMatrix).[r * 7 + 5] + (!game_state.BoardMatrix).[r * 7 + 6]
        checkResult(result.Value)
    // CLM CHECK
    for c = 0 to 6 do
        result.Value <- (!game_state.BoardMatrix).[0 * numCol + c] + (!game_state.BoardMatrix).[1 * numCol + c] + (!game_state.BoardMatrix).[2 * numCol + c] + (!game_state.BoardMatrix).[3 * numCol + c];
        checkResult(result.Value)
        result.Value <- (!game_state.BoardMatrix).[1 * numCol + c] + (!game_state.BoardMatrix).[2 * numCol + c] + (!game_state.BoardMatrix).[3 * numCol + c] + (!game_state.BoardMatrix).[4 * numCol + c];
        checkResult(result.Value)
    // DGN CHECK
    for d = 0 to 3 do
        result.Value <- (!game_state.BoardMatrix).[4 * numCol + d] + (!game_state.BoardMatrix).[3 * numCol + d + 1] + (!game_state.BoardMatrix).[2 * numCol + d + 2] + (!game_state.BoardMatrix).[1 * numCol + d + 3];
        checkResult(result.Value)
        result.Value <- (!game_state.BoardMatrix).[3 * numCol + d] + (!game_state.BoardMatrix).[2 * numCol + d + 1] + (!game_state.BoardMatrix).[1 * numCol + d + 2] + (!game_state.BoardMatrix).[0 * numCol + d + 3];
        checkResult(result.Value)
        result.Value <- (!game_state.BoardMatrix).[0 * numCol + d] + (!game_state.BoardMatrix).[1 * numCol + d + 1] + (!game_state.BoardMatrix).[2 * numCol + d + 2] + (!game_state.BoardMatrix).[3 * numCol + d + 3];
        checkResult(result.Value)
        result.Value <- (!game_state.BoardMatrix).[1 * numCol + d] + (!game_state.BoardMatrix).[2 * numCol + d + 1] + (!game_state.BoardMatrix).[3 * numCol + d + 2] + (!game_state.BoardMatrix).[4 * numCol + d + 3];
        checkResult(result.Value)


let clearState (wh : int, th : int) =
    let empty_state = 
        {
            BoardMatrix = Variable(fun () -> Array.zeroCreate (numCol * numRow))
            BallArray =  Variable(fun () -> Array.zeroCreate (numCol * numRow))
            row = Variable(0,0)
            col = Variable(3,3)
        }
    game_state.BallArray := !empty_state.BallArray
    game_state.BoardMatrix := !empty_state.BoardMatrix
    game_state.col := !empty_state.col
    game_state.row := !empty_state.row

    let random = System.Random()
    let randomVal = random.Next(0, 10)

    if randomVal > 5 then
        turn :=  - 1
    else
        turn :=  1

    Casanova.commit_variable_updates()

    checkBall.Value <- true
    wHeight.Value <- wh
    tHeight.Value <- th

    for i = 0 to ((!game_state.BallArray).Length - 1) do
        let emptyBall =
            {Position = Variable(0.0<m>,0.0<m>); 
            Velocity = Variable(0.0<v>, 0.0<v>); 
            Acceleration = Variable(0.0<a>,0.0<a>); 
            Time = Variable(0.0<s>,0.0<s>);
            Active = ref false;
            Player = 0;
            Col = 0;
            Row = 0}
        
        let emptyBallArr = !game_state.BallArray
        emptyBallArr.SetValue(emptyBall, i)
        game_state.BallArray := emptyBallArr


let private main () =
    let rec check_victory() =
        co{
            do! wait (1 |> float)
            checkVictory()
            return ()
        }
    let rec make_move() = 
        co{
            do! yield_
            make_move_callback()
            return ()
        }
    (repeat_ (make_move()) .||> repeat_ (check_victory()))

let mutable update_main = 
    main()
let update_script() = update_main <- update_ai update_main